/*

Title:			ledtro.c 
Target:			Atmel ATmega168 
Environment:	AVR-GCC
Purpose:		Win crap in a demoparty
Application:	Demo

Rotozoomer, effects, aubilenon & zsazs.

IO routines from Windell H Oskay, which was in turn taken from David Gustafik's 
code.  Both were GPL and therefore so is this.


    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/


#include <math.h>
#include <avr/io.h> 
#include <stdlib.h> 
#include <avr/eeprom.h>
#include <stdint.h>
#include <string.h>
//#include <stdio.h>

#define F_CPU 20000000UL
#include <util/delay.h>

#include <avr/pgmspace.h>
const prog_uint8_t big_bitmap[128][16] =  
//#include "monkey.data"
#include "bowlingball.data"
;

const prog_uint8_t sqrts[157] = 
#include "sqrt1256.data"
;

const prog_int8_t sine[256] = 
#include "sine256.data"
;

const prog_uint8_t font[][4] = 
#include "font.data"
;
const prog_uint16_t offsets[37] = {
#include "offsets.data"
};

const prog_uint32_t ledtro[25] = 
#include "ledtro.data"
;
/////////////////////////////////////////////////////////////////////////


signed short xCursor = 0;
signed short yCursor = 12;
  
typedef union { 
    unsigned long int li; 
    struct {unsigned char left, middle, right, top;}; 
} pixel_row;


struct {
    unsigned short int u, v;  // 7.9 fixed point
    unsigned short int startu, startv;  // top right!
    signed short int xdu, xdv; // 7.9 fixed point
    signed short int ydu, ydv; // 7.9 fixed point
    signed short slime, slimespeed;
} rotostate;

int frame = 0;



struct {
    const unsigned char * text;
    int index ;
    int ofs, end;
} font_state = {"        LEDTRO     BY    IAN    JESSE    AND     ANDY      "
"THIS WON FIRST PLACE AT THE SAN FRANCISCO MINDEMOPARTY      "
"IT IS RUNNING A TWENTY MHZ AVR MICROCONTROLLER PROGRAMMED IN C      "
"THE PLATFORM WAS DESIGNED BY EVIL MAD SCIENTIST LABORATORIES       ", 0, 0, 7};

inline int get_at(u,v) {
    if (pgm_read_byte(&big_bitmap[v][u/8]) & (0x80 >> (u & 7))) return 1;
    return 0;
}

struct metaball {
	unsigned short int x, y;
};
	
struct metaball mb1 = {12, 12};
struct metaball mb2 = {12, 12};
struct metaball mb3 = {12, 12};

pixel_row GetRowScroller(long int which_row) { // scroller, texst
    pixel_row pr = {0};
    
    long int pb = 1;
    int x = 0;
    const char * cur = font_state.text + font_state.index;
    int cur_ofs = font_state.ofs;
    int end = font_state.end;

    while (x < 25) {
        int y = which_row + ((signed char) 
            pgm_read_byte(&sine[(unsigned char)(x*10 + frame)]) / 32);
        if (cur_ofs >= end) {
            ++cur;
            cur_ofs = pgm_read_word(&offsets[*cur - 'A']);
            end = (*cur == ' ' )? 5 : pgm_read_word(&offsets[*cur - 'A' + 1]);
        }
        if (*cur != ' ')  {
            if (!(pgm_read_byte(&font[cur_ofs] [3-(y >> 3)]) 
                & ( 0x80 >> (7-(y & 7)))))
                pr.li |= pb;
        }
        ++ cur_ofs;
        pb  <<= 1;
        x++;
    }
    return pr;

}

pixel_row GetRowMongo(long int which_row) { // mongoballs!
    pixel_row pr = {0};

    long int pb = 1;
    int x = 0;
    for (x = 0; x < 25; ++x) {
		int d1 = (mb1.x - x) * (mb1.x - x) + (mb1.y - which_row) * (mb1.y - which_row);
		int d2 = (mb2.x - x) * (mb2.x - x) + (mb2.y - which_row) * (mb2.y - which_row);
		if ((1 - d1) * (1 - d1) + (1 - d2) * (1 - d2) < 100) {
            pr.li |= pb;
		}
        pb <<= 1;
	}
	return pr;
}

#define max(x,y)        ((x) >= (y) ? (x) : (y))


pixel_row GetRowRoto(long int which_row) {  // rotozoomer 
    pixel_row pr = {0};
    unsigned short int u = rotostate.u + rotostate.ydu * which_row;
    unsigned short int v = rotostate.v + rotostate.ydv * which_row;
    long int pb = 0x10000000; 
    long int x = 0;
    while (pb) {
        ++x;
        if (  // for a traditional rotozoomer do get_at(u>>9, v>>9))
              // that looked too busy so we just xored some bits of u and v 
              // to get a checkerboard
            (((u+ pgm_read_byte(&sine[(which_row * rotostate.slimespeed >> 8) & 0xff]) * rotostate.slime) >> 12) ^ 
             ((v + pgm_read_byte(&sine[(x* rotostate.slimespeed>> 8) & 0xff]) * rotostate.slime)>> 12)) & 4)        
            pr.li |= pb;
        u += rotostate.xdu;
        v += rotostate.xdv;
        pb >>= 1;
    }
    return pr;
}

#define FIXEDFACTOR 256
#define FIXEDSHIFT 8
pixel_row GetRowInterference(int which_row) {
    pixel_row pr = {0};
    long int dy1 = which_row - 20;
    long int dy2 = (yCursor - FIXEDFACTOR * which_row);
    long int pb = 1;
    long int x;
    dy1 = dy1 * dy1;
    dy2 = dy2 * dy2 >> FIXEDSHIFT;
    for (x = 0; x < 25; ++x) {
        long int d1 = (x - 15) * (x - 15) + dy1;
        long int d2 = ((((xCursor - x*FIXEDFACTOR) * (xCursor - x*FIXEDFACTOR))>>FIXEDSHIFT) + dy2) >> FIXEDSHIFT;
        d1 = pgm_read_byte(&sqrts[(d1) >> 3]) & (1 << (d1&7)) ? 1 : 0;
        d2 = pgm_read_byte(&sqrts[(d2) >> 3]) & (1 << (d2&7)) ? 1 : 0;
        if (d1 ^ d2)
            pr.li |= pb;
        pb <<= 1;
    }
    return pr;
}

pixel_row GetRowPan(int which_row) { 
    char realX = xCursor - 7;
    const prog_uint8_t *addr = &big_bitmap[which_row + yCursor][realX/8];
    int ofs = realX & 7;
    pixel_row pr;
    pr.li = 
        (
         (long) pgm_read_byte(addr+4) >> (8 - ofs) |
         (long) pgm_read_byte(addr+3) << ofs| 
         (long) pgm_read_byte(addr+2) << (8 + ofs) | 
         (long) pgm_read_byte(addr+1) << (16 + ofs) |
         (long) pgm_read_byte(addr+0) << (24 + ofs));
	_delay_us(100);
    return pr;
                // << (xCursor & 31) ) | 
           //(pgm_read_dword(addr+1) >> (32 - (xCursor & 31)));

}
 
/////////////////////////////////////////////////////////////////////////




/////////////////////////////////////////////////////////////////////////

void SPI_TX (char cData)
{
//Start Transmission
SPDR = cData;
//Wait for transmission complete:
while (!(SPSR & _BV(SPIF))) ;
	
}

enum {MONGO, ROTO, PAN, SCROLLER, SPLASH} demofunc;

unsigned char scrolloff;

void DisplayLEDs()
{

    unsigned int j;
    unsigned char out1=0,out2=0,out3=0,out4=0;
    pixel_row dtemp = {0};

    j = 0;

    while (j < 25) 
    	{
	
    	if (j == 0)
          PORTD = 160;
    	else if (j < 16)
    	  PORTD = j;
    	else
    	  PORTD = (j - 15) << 4;  

		if (j + scrolloff < 26) {
			switch(demofunc) {
				case MONGO:		
					dtemp = GetRowMongo(j + scrolloff); 
					break;
				case ROTO:
					dtemp = GetRowRoto(j + scrolloff);
					break;
				case PAN:
					dtemp = GetRowPan(j + scrolloff);
					break;
				case SCROLLER:
					dtemp = GetRowScroller(j + scrolloff);
					break;
				case SPLASH:
					dtemp.li = pgm_read_dword(&ledtro[j]);
					break;
			}
		} else {
			dtemp.li = 0;
		}
		
		out4 = dtemp.left;
		out3 = dtemp.middle;
		out2 = dtemp.right;	 
		out1 = dtemp.top; 	
	
        if (!j) {
            ++frame;
          //  out1 = frame & 1;   // framerate blinker
        }
		
		
        SPI_TX(out1);
        SPI_TX(out2);
        SPI_TX(out3);
        PORTD = 0;  // Turn displays off
        SPI_TX(out4);


        PORTB |= _BV(1);		//Latch Pulse 
        PORTB &= ~( _BV(1));
 
        j++;
    }


}



int main(void)
{
	
    unsigned char temp = 0;
    unsigned char InputRegOld = 0, InputReg = 0;

	srand(eeprom_read_word((uint16_t *) 2));
	for(temp = 0; temp != 255; temp++)
	{
		TCNT0 = rand();
	}
	eeprom_write_word((uint16_t *) 2,rand());
  
    PORTD = 0U;			//All B Input
    DDRD = 255U;		// All D Output

    PORTB = 1;		// Pull up on ("OFF/SELECT" button)
    PORTC = 255U;	// Pull-ups on C

    DDRB = 254U;  // B0 is an input ("OFF/SELECT" button)
    DDRC = 0;		//All inputs

    ////SET MOSI, SCK Output, all other SPI as input:
    DDRB = ( 1 << 3 ) | (1 << 5) | (1 << 2) | (1 << 1);

    //ENABLE SPI, MASTER, CLOCK RATE fck/4:		//TEMPORARY:: 1/128
    SPCR = (1 << SPE) | ( 1 << MSTR );//| ( 1 << SPR0 );//| ( 1 << SPR1 ) | ( 1 << CPOL ); 

    SPI_TX(0);
    SPI_TX(0);
    SPI_TX(0);

    PORTB |= _BV(1);		//Latch Pulse 
    PORTB &= ~( _BV(1));

	_delay_ms(25);
 
    InputRegOld = (PINC & 31) | ((PINB & 1)<<5);  

 

    float angle = 0, magnitude = 512;
    
	static float t = -5000;

	demofunc = SPLASH;

	while(1)    //Main Loop
	{
    	// Some routines follow to do things if the optional buttons are installed-- a simple editor is implemented.
	 
    	InputReg = (PINC & 31) | ((PINB & 1)<<5);    // Input reg measures OFF ( bit 5) and b1-b5 (bits 0-4). 
    	InputRegOld ^= InputReg;    // InputRegOld is now nonzero if there has been a change.
	
    	if (InputRegOld) {
	
    	InputRegOld &= InputReg;  // InputRegOld is now nonzero if the change was to the button-released (not-pulled-down) state.
    								// i.e., the bit that was high after the XOR corresponds to a bit that is presently high.
    								// The checks that follow will handle MULTIPLE buttons being pressed and unpressed at the same time.
    	if (InputRegOld & 1)   //b1 "ANY" button is pressed
    		{
				if (demofunc == SPLASH) {
					t = 1;
				} else {
					t += 5000 - (int)t % 5000;
				}
    		}  
    	if (InputRegOld & 2)   //b2 "left" button is pressed
    		{
		        xCursor ++;
                angle += 0.1;
    		}  
    	if (InputRegOld & 4)   //b3 "down" button is pressed	
    		{
		
                yCursor++;
                magnitude *= 1.05;

    		}  			
    	if (InputRegOld & 8)   //b4 "up" button is pressed		
    		{
                yCursor--;
                magnitude /= 1.05;

    		}  
    	if (InputRegOld & 16)	 //b5 "right" button is pressed
    		{
		        xCursor --;
                angle -= 0.1;
    		}
    	if (InputRegOld & 32)	// s2 "Off/Select" button is pressed... 
    		{
				t -= (int)t % 5000;
    		}  
		
    	}

        InputRegOld = InputReg;

        //static long t = 0;
		mb1.x = sin((float)t/100.) * 10 + 12;
		mb1.y = cos((float)t/81.) * 10 + 12;
		mb2.x = sin((float)t/120.) * 15 + 12;
		mb2.y = cos((float)t/171.) * 15 + 12;
//		mb3.x = sin((float)t/72) * 5 + 12;
//		mb3.y = cos(t/199) * 5 + 12;
		if (t < 0); else
		if (t < 4350) {
			demofunc = ROTO;
			angle = cos(t / 1141) * 50;
			magnitude = sin(t/1275) * 128 + 140;
			rotostate.xdu = cos(angle) * magnitude;
			rotostate.xdv = -sin(angle) * magnitude;
			rotostate.ydu = rotostate.xdv;
			rotostate.ydv = -rotostate.xdu;
			rotostate.slime = sin(t/1490) * 40 * 16;
			rotostate.slimespeed = sin(t/260) * 1200;
			if (t > 94500) {
				if((int)t % 20 == 0) {
					scrolloff += 1;		
				}
			}
		} else if (t < 10000) {
            if (demofunc != MONGO) {
                scrolloff = 0;
			    demofunc = MONGO;
            }
			mb1.x = sin(t/100) * 10 + 12;
			mb1.y = cos(t/81) * 10 + 12;
			mb2.x = sin(t/120) * 15 + 12;
			mb2.y = cos(t/171) * 15 + 12;
			if (t > 9500) {
				if((int)t % 20 == 0) {
					scrolloff += 1;		
				}
			}
		} else if (t < 15000) {
			if (demofunc != PAN) {
                demofunc = PAN;
                scrolloff = 0;
            }
			//rotostate.u = sin(t / 500) * 512 * 51.5 + 51.5 * 512; // for 128x128
			//rotostate.v = sin(t / 597) * 512 * 51.5 + 51.5 * 512; // for 128x128
			//rotostate.u = sin(t / 100) * 256 * 25.75 + 25.75 * 256;
			//rotostate.v = sin(t / 149) * 256 * 20.75 + 20.75 * 256;
			xCursor = sin(t / 300) * 24 + 54;
			yCursor = sin(t / 457) * 24 + 54;
			//xCursor = (sin(t/90) * 10 + 10) * FIXEDFACTOR;
			//yCursor = (sin(t/111) * 10 + 10) * FIXEDFACTOR;
			if (t > 14500) {
				if((int)t % 20 == 0) {
					scrolloff += 1;		
				}
			}
		} else if (t < 55000) {
            if (demofunc != SCROLLER) {
			    scrolloff = 0;
			    demofunc = SCROLLER;
             }
			if ( 0 == (int)t % 5) {
				font_state.ofs++;
				if (font_state.ofs >= font_state.end) {
					font_state.index++;                
					if (font_state.index > strlen(font_state.text) - 8) {
						demofunc = SPLASH;
                        font_state.ofs = 0;
                        font_state.index = 0;
						t = -5000;
					}
					if (font_state.text[font_state.index] != ' ') {
						font_state.ofs = pgm_read_word(&offsets[font_state.text[font_state.index] - 'A']);
						font_state.end = pgm_read_word(&offsets[font_state.text[font_state.index] - 'A' + 1]);
					} else  {
						font_state.ofs = 0;
						font_state.end = 7;
					}            
				}
			}
		} 	
		
//		if(t)	
			t++;
        
        DisplayLEDs();
    }		
}

